The goal of this notebook is to compare pseudobulk and bulk calculations to determine which calculation flavors we should proceed with. We compare two pseudobulk and two bulk measures:

We’ll explore expression distributions, the relationships between bulk and pseudobulk measures, and we’ll also have a brief look at whether there are any genes with strong disagreement between bulk and single-cell.

Setup

renv::load()

library(ggplot2)
theme_set(theme_bw())

Paths

data_dir <- here::here("analysis", "pseudobulk-bulk-prediction", "data")
tpm_dir <- file.path(data_dir, "tpm")
pseudobulk_dir <- file.path(data_dir, "pseudobulk")
bulk_counts_file <- file.path(data_dir, "scpca_data", "normalized_bulk_counts.rds")

tpm_files <- list.files(
  path = tpm_dir,
  full.names = TRUE,
  pattern = "-tpm\\.tsv$"
)
tpm_names <- stringr::str_split_i(basename(tpm_files), pattern = "-", i = 1)
names(tpm_files) <- tpm_names


pseudobulk_files <- list.files(
  path = pseudobulk_dir,
  full.names = TRUE,
  pattern = "-pseudobulk\\.tsv$"
)
pseudobulk_names <- stringr::str_split_i(basename(pseudobulk_files), pattern = "-", i = 1)
names(pseudobulk_files) <- pseudobulk_names

# Make sure we have the same projects, in the same order
stopifnot(
  all.equal(names(tpm_files), names(pseudobulk_files))
)

Read and prepare input data

We’ll make both a long and wide version of the data for convenience throughout the notebook.

project_df_list <- purrr::map2(
  tpm_files, 
  pseudobulk_files, 
  \(tpm_file, pseudo_file) {
    
    tpm_df <- readr::read_tsv(tpm_file, show_col_types = FALSE) |>
      #  TPM needs to be in log2 space
      dplyr::mutate(expression = log2(expression))
    
    pseudo_df <- readr::read_tsv(pseudo_file, show_col_types = FALSE) 
    
    dplyr::bind_rows(
      tpm_df, 
      pseudo_df
    ) |>
      tidyr::pivot_wider(
        names_from = expression_type, 
        values_from = expression
      )
  }
)

We’ll read in the bulk counts data and combine with the rest of the data.

# we only want to keep these samples from the bulk counts
present_samples <- project_df_list |>
  purrr::map(
    \(df) {
      df |> 
        dplyr::pull(sample_id) |> 
        unique()
    }
  ) |>
  purrr::reduce(c)

# Make a list of data frames of bulk counts, normalized by DESeq2
bulk_df_list <- readr::read_rds(bulk_counts_file) |>
  purrr::map(
    \(df) {
      df |>
        dplyr::filter(sample_id %in% present_samples) |>
        dplyr::rename(ensembl_id = gene_id)
    }
  )

# Combine the data
project_df_list <- purrr::map2(
  bulk_df_list, 
  project_df_list, 
  \(df_counts, df_main) {
    
    df_main |>
      dplyr::left_join(df_counts, by = c("ensembl_id", "sample_id"))
  }
)

Full distributions

First, we’ll visualize distributions of all quantities:

plot_df <- project_df_list |>
  purrr::list_rbind(names_to = "project_id") |> 
  tidyr::pivot_longer(
    c(-project_id, -ensembl_id, -sample_id), 
    names_to = "expression_type", 
    values_to = "expression"
  )

ggplot(plot_df) + 
  aes(x = expression, fill = expression_type) + 
  geom_density(alpha = 0.5) + 
  scale_fill_brewer(palette = "Dark2") + 
  facet_grid(
    rows = vars(expression_type), 
    cols = vars(project_id),
    scales = "free_y"
  ) +
  theme(legend.position = "none")

We see big spikes at zero for pseudobulk, not surprisingly. Due to the different transformation approaches, the pseudobulk_deseq version has some negatives for fractional values, but the other quantities have a lower bound of zero. All around, distributions are concetrated range from their lower bound to around 20, so it’s nice to know pseudobulk and bulk are definitely on the same scale. In the bulk counts, SCPCP000017 seems to have a much larger peak at zero compared to other projects.

Let’s clean up for memory here.

rm(plot_df)
gc()
            used   (Mb) gc trigger   (Mb) limit (Mb)  max used   (Mb)
Ncells   1575874   84.2    2749681  146.9         NA   2749681  146.9
Vcells 184919584 1410.9  576446622 4398.0      51200 576011884 4394.7

Relationship between quantities

This section will look at the relationship among quantities:

Compare pseudobulk measures

project_df_list |>
  purrr::imap(
    \(df, project_id) {
      
      ggplot(df) + 
        aes(x = pseudobulk_deseq, y = pseudobulk_log_counts) + 
        geom_point(alpha = 0.2, size = 0.5) + 
        geom_smooth(method = "lm", linewidth = 0.5) +
        geom_abline(linewidth = 0.5, color = "red") + 
        facet_wrap(vars(sample_id), nrow = 5) +
        ggtitle(project_id) 

    }
  ) |>
  patchwork::wrap_plots(ncol = 1)

These quantities are exceptionally similar with these differences:

  • Driven by different normalization approaches, genes with very low to zero expression
  • In a handful of samples (1-2 per project), pseudobulk_log_counts appears to have a higher proportion of low to zero counts, and throughout has lower values than pseudobulk_deseq.

Compare pseudobulk to bulk

Since these quantities here are so similar, these scatterplots will show only bulk comparisons to pseudobulk_deseq.

These plots will show:

  • Left panel: bulk TPM ~ pseudobulk_deseq
  • Right panel: bulk counts ~ pseudobulk_deseq
# Helper function to visualize scatterplots with geom_bin_2d()
make_binned_scatterplots <- function(df, project_id, nbins, facet_rows) {
  p1 <- ggplot(df) + 
    aes(x = pseudobulk_deseq, y = bulk_tpm) + 
    geom_bin_2d(bins = nbins) + 
    geom_smooth(method = "lm", alpha = 0.8, linewidth = 0.5) +
    geom_abline(alpha = 0.8, linewidth = 0.5, color = "red") + 
    facet_wrap(vars(sample_id), nrow = facet_rows) +
    ggtitle("bulk_tpm ~ deseq") 
  
  p2 <- ggplot(df) + 
    aes(x = pseudobulk_deseq, y = bulk_counts) + 
    geom_bin_2d(bins = nbins) + 
    geom_smooth(method = "lm", alpha = 0.8, linewidth = 0.5) +
    geom_abline(alpha = 0.8, linewidth = 0.5, color = "red") + 
    facet_wrap(vars(sample_id), nrow = facet_rows) +
    ggtitle("bulk_counts ~ deseq") 


  patchwork::wrap_plots(p1, p2, ncol = 2) + patchwork::plot_annotation(title = project_id)
}
make_binned_scatterplots(
  project_df_list$SCPCP000001, 
  project_id = "SCPCP000001",
  nbins = 15,
  facet_rows = 6
)

make_binned_scatterplots(
  project_df_list$SCPCP000002, 
  project_id = "SCPCP000002",
  nbins = 15,
  facet_rows = 6
)

make_binned_scatterplots(
  project_df_list$SCPCP000006, 
  project_id = "SCPCP000006",
  nbins = 15,
  facet_rows = 9
)

make_binned_scatterplots(
  project_df_list$SCPCP000009, 
  project_id = "SCPCP000009",
  nbins = 15,
  facet_rows = 1
)

make_binned_scatterplots(
  project_df_list$SCPCP000017, 
  project_id = "SCPCP000017",
  nbins = 40,
  facet_rows = 7
)

  • bulk_counts generally has much closer to a 1:1 relationship with pseudobulk compared to bulk_tpm
    • This is nearly universally the case for SCPCP000001, SCPCP000002, and SCPCP000006, and while true for SCPCP000009 and SCPCP000017, it is less pronounced
  • Relationships in general are weakest for SCPCP000017, but the situation does look better with bulk_counts

Statistics

Let’s nail this down further with correlations comparing each measure for bulk with each measure for pseudobulk. We’ll perform correlations on a per-sample basis, both parametric and non-parametric, display some correlations below both as boxplots and the full table.

# Helper function to run correlations
model_samples <- function(id, df) {
  sample_df <- df |>
    dplyr::filter(sample_id == id) 
  
  #### Bulk tpm
  df_deseq <- sample_df |>
    dplyr::filter(is.finite(pseudobulk_deseq), is.finite(bulk_tpm))
  r_deseq_tpm <- cor(df_deseq$bulk_tpm, df_deseq$pseudobulk_deseq, method = "spearman")
  rho_deseq_tpm <- cor(df_deseq$bulk_tpm, df_deseq$pseudobulk_deseq, method = "pearson")

  df_log_counts <- sample_df |>
      dplyr::filter(is.finite(pseudobulk_log_counts), is.finite(bulk_tpm))
  r_logcounts_tpm <- cor(df_deseq$bulk_tpm, df_deseq$pseudobulk_log_counts, method = "spearman")
  rho_logcounts_tpm <- cor(df_deseq$bulk_tpm, df_deseq$pseudobulk_log_counts, method = "pearson")
      
  #### Bulk counts
  df_deseq <- sample_df |>
    dplyr::filter(is.finite(pseudobulk_deseq), is.finite(bulk_counts))
  r_deseq_counts <- cor(df_deseq$bulk_counts, df_deseq$pseudobulk_deseq, method = "spearman")
  rho_deseq_counts <- cor(df_deseq$bulk_counts, df_deseq$pseudobulk_deseq, method = "pearson")

  df_log_counts <- sample_df |>
      dplyr::filter(is.finite(pseudobulk_log_counts), is.finite(bulk_counts))
  r_logcounts_counts <- cor(df_deseq$bulk_counts, df_deseq$pseudobulk_log_counts, method = "spearman")
  rho_logcounts_counts <- cor(df_deseq$bulk_counts, df_deseq$pseudobulk_log_counts, method = "pearson")
      
  # Tabulate and return some fit stats
  data.frame(
    r =      c(r_deseq_tpm,    r_logcounts_tpm,    r_deseq_counts,   r_logcounts_counts),
    rho =    c(rho_deseq_tpm,  rho_logcounts_tpm,  rho_deseq_counts, rho_logcounts_counts),
    bulk =   c("bulk_tpm",    "bulk_tpm",          "bulk_counts",    "bulk_counts"), 
    pseudo = c("deseq",       "log_counts",        "deseq",          "log_counts")
  ) |>
    tidyr::pivot_longer(
      c(r, rho), 
      names_to = "measure", 
      values_to = "correlation"
    )
}

stats_df <- project_df_list |>
  purrr::map(
    \(df) {
      
      # We need to map over sample ids now
      samples <- unique(df$sample_id)
      names(samples) <- samples
      
      fit_table <- samples |>
        purrr::map(model_samples, df) |>
        purrr::list_rbind(names_to = "sample_id")
      
      return(fit_table)

    }
  ) |>
  # now, combine all projects into a single table
  purrr::list_rbind(names_to = "project_id") |>
  dplyr::mutate(comparison = glue::glue("{bulk}-{pseudo}")) |>
  dplyr::select(-bulk, -pseudo)


ggplot(stats_df) + 
  aes(x = comparison, fill = comparison, y = correlation) + 
  geom_boxplot(linewidth = 0.25, outlier.size = 0.25)+
  facet_grid(
    rows = vars(project_id), 
    cols = vars(measure)) + 
  theme(
    legend.position = "none", 
    axis.text.x = element_text(angle = 30, hjust = 1)
  )

  • Relationships between pseudobulk and bulk counts are exceptionally similar here, and further we are generally lying along the 1:1 line.
  • For all projects, the nonparametric approach yields marginally higher correlations, but often marginally
  • Using a nonparametric test yields stronger correlations for bulk_counts not but for bulk_tpm
  • For SCPCP000001, SCPCP000002, and SCPCP000006, the correlations are generally similar regardless of which quantities are being compared and which statistic is used, although there are some small differences (not likely significant)
  • For SCPCP000009 (again, only 3 samples here!) and SCPCP000017, bulk_counts tends to outperform bulk_tpm for either pseudobulk measure

The underlying correlations are here:

stats_df

Disagreeing expression

Based on performances of quantities above, this section considers specifically bulk_counts and pseudobulk_log_counts.

Next, we’ll take a quick look at cases where one modality has zero expression and the other doesn’t. In these cases, if expression is generally high, we have evidence of disagreement/discrepancy between bulk and single-cell that may be interesting to investigate. In this notebook, we’ll just a sense of how much “there is there,” and we’ll leave the in-depth look into any such genes for a subsequent notebook.

In this section, we’ll also use a threshold of 1e-12 for zero here.

Bulk counts when single-cell is zero

plot_df <- purrr::list_rbind(project_df_list, names_to = "project_id") |>
  dplyr::filter(
    pseudobulk_log_counts <= 1e-12, 
    bulk_counts > 1e-12
  )  
  
ggplot(plot_df) + 
  aes(x = sample_id, y = bulk_counts) + 
  geom_boxplot(outlier.size = 0.25) +
  facet_wrap(vars(project_id), ncol = 1, scale = "free") +
  theme(axis.text.x = element_blank())

Single-cell when bulk counts is zero

plot_df <- purrr::list_rbind(project_df_list, names_to = "project_id") |>
  dplyr::filter(
    bulk_counts <= 1e-12, 
    pseudobulk_log_counts > 1e-12
  )  
  
ggplot(plot_df) + 
  aes(x = sample_id, y = pseudobulk_log_counts) + 
  geom_boxplot(outlier.size = 0.25) +
  facet_wrap(vars(project_id), ncol = 1, scale = "free") +
  theme(axis.text.x = element_blank())

From both comparisons, there’s a decent number of genes with high expression in one modality and essentially zero in the other. A more careful investigation here look into what exactly these genes are, and whether they have some biological relationship that might suggest modalities are picking up different information.

Session info

sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS 15.3

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] ggplot2_3.5.1

loaded via a namespace (and not attached):
 [1] sass_0.4.9          generics_0.1.3      tidyr_1.3.1        
 [4] renv_1.0.11         stringi_1.8.4       lattice_0.22-6     
 [7] hms_1.1.3           digest_0.6.37       magrittr_2.0.3     
[10] evaluate_1.0.1      grid_4.4.0          RColorBrewer_1.1-3 
[13] fastmap_1.2.0       Matrix_1.7-1        rprojroot_2.0.4    
[16] jsonlite_1.8.9      BiocManager_1.30.25 mgcv_1.9-1         
[19] purrr_1.0.2         scales_1.3.0        jquerylib_0.1.4    
[22] cli_3.6.3           rlang_1.1.4         crayon_1.5.3       
[25] bit64_4.5.2         munsell_0.5.1       splines_4.4.0      
[28] withr_3.0.2         cachem_1.1.0        yaml_2.3.10        
[31] tools_4.4.0         parallel_4.4.0      tzdb_0.4.0         
[34] dplyr_1.1.4         colorspace_2.1-1    here_1.0.1         
[37] vctrs_0.6.5         R6_2.5.1            lifecycle_1.0.4    
[40] stringr_1.5.1       bit_4.5.0.1         vroom_1.6.5        
[43] pkgconfig_2.0.3     pillar_1.10.0       bslib_0.8.0        
[46] gtable_0.3.6        glue_1.8.0          xfun_0.49          
[49] tibble_3.2.1        tidyselect_1.2.1    knitr_1.49         
[52] farver_2.1.2        htmltools_0.5.8.1   nlme_3.1-166       
[55] patchwork_1.3.0     rmarkdown_2.29      labeling_0.4.3     
[58] readr_2.1.5         compiler_4.4.0     
LS0tCnRpdGxlOiAiSW5pdGlhbCBleHBsb3JhdGlvbiBhbmQgY29tcGFyaXNvbiBvZiBwc2V1ZG9idWxrIHZzLiBidWxrIGV4cHJlc3Npb24iCmF1dGhvcjogU3RlcGhhbmllIEouIFNwaWVsbWFuCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoKVGhlIGdvYWwgb2YgdGhpcyBub3RlYm9vayBpcyB0byBjb21wYXJlIHBzZXVkb2J1bGsgYW5kIGJ1bGsgY2FsY3VsYXRpb25zIHRvIGRldGVybWluZSB3aGljaCBjYWxjdWxhdGlvbiBmbGF2b3JzIHdlIHNob3VsZCBwcm9jZWVkIHdpdGguCldlIGNvbXBhcmUgdHdvIHBzZXVkb2J1bGsgYW5kIHR3byBidWxrIG1lYXN1cmVzOgoKKiBQc2V1ZG9idWxrOgogICogYHBzZXVkb2J1bGtfZGVzZXFgOiByYXcgY291bnRzIHN1bW1lZCBhbmQgbm9ybWFsaXplZCB3aXRoIGBERVNlcTJgCiAgKiBgcHNldWRvYnVsa19sb2dfY291bnRzYDogcmF3IGNvdW50cyBzdW1tZWQgYW5kIHdpdGggYSBgbG9nMih4KzEpYCB0cmFuc2Zvcm1hdGlvbgoqIEJ1bGs6CiAgKiBgYnVsa19jb3VudHNgOiBCdWxrIHJhdyBjb3VudHMgbm9ybWFsaXplZCB3aXRoIGBERVNlcTJgCiAgKiBgYnVsa190cG1gOiBUUE0gYnVsayBkYXRhIG9uIGEgYGxvZzJgIHNjYWxlCiAgCiAgCldlJ2xsIGV4cGxvcmUgZXhwcmVzc2lvbiBkaXN0cmlidXRpb25zLCB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIGJ1bGsgYW5kIHBzZXVkb2J1bGsgbWVhc3VyZXMsIGFuZCB3ZSdsbCBhbHNvIGhhdmUgYSBicmllZiBsb29rIGF0ICB3aGV0aGVyIHRoZXJlIGFyZSBhbnkgZ2VuZXMgd2l0aCBzdHJvbmcgZGlzYWdyZWVtZW50IGJldHdlZW4gYnVsayBhbmQgc2luZ2xlLWNlbGwuIAoKCiMjIFNldHVwCgpgYGB7ciBzZXR1cH0KcmVudjo6bG9hZCgpCgpsaWJyYXJ5KGdncGxvdDIpCnRoZW1lX3NldCh0aGVtZV9idygpKQpgYGAKCiMjIyBQYXRocwoKYGBge3IgcGF0aHN9CmRhdGFfZGlyIDwtIGhlcmU6OmhlcmUoImFuYWx5c2lzIiwgInBzZXVkb2J1bGstYnVsay1wcmVkaWN0aW9uIiwgImRhdGEiKQp0cG1fZGlyIDwtIGZpbGUucGF0aChkYXRhX2RpciwgInRwbSIpCnBzZXVkb2J1bGtfZGlyIDwtIGZpbGUucGF0aChkYXRhX2RpciwgInBzZXVkb2J1bGsiKQpidWxrX2NvdW50c19maWxlIDwtIGZpbGUucGF0aChkYXRhX2RpciwgInNjcGNhX2RhdGEiLCAibm9ybWFsaXplZF9idWxrX2NvdW50cy5yZHMiKQoKdHBtX2ZpbGVzIDwtIGxpc3QuZmlsZXMoCiAgcGF0aCA9IHRwbV9kaXIsCiAgZnVsbC5uYW1lcyA9IFRSVUUsCiAgcGF0dGVybiA9ICItdHBtXFwudHN2JCIKKQp0cG1fbmFtZXMgPC0gc3RyaW5ncjo6c3RyX3NwbGl0X2koYmFzZW5hbWUodHBtX2ZpbGVzKSwgcGF0dGVybiA9ICItIiwgaSA9IDEpCm5hbWVzKHRwbV9maWxlcykgPC0gdHBtX25hbWVzCgoKcHNldWRvYnVsa19maWxlcyA8LSBsaXN0LmZpbGVzKAogIHBhdGggPSBwc2V1ZG9idWxrX2RpciwKICBmdWxsLm5hbWVzID0gVFJVRSwKICBwYXR0ZXJuID0gIi1wc2V1ZG9idWxrXFwudHN2JCIKKQpwc2V1ZG9idWxrX25hbWVzIDwtIHN0cmluZ3I6OnN0cl9zcGxpdF9pKGJhc2VuYW1lKHBzZXVkb2J1bGtfZmlsZXMpLCBwYXR0ZXJuID0gIi0iLCBpID0gMSkKbmFtZXMocHNldWRvYnVsa19maWxlcykgPC0gcHNldWRvYnVsa19uYW1lcwoKIyBNYWtlIHN1cmUgd2UgaGF2ZSB0aGUgc2FtZSBwcm9qZWN0cywgaW4gdGhlIHNhbWUgb3JkZXIKc3RvcGlmbm90KAogIGFsbC5lcXVhbChuYW1lcyh0cG1fZmlsZXMpLCBuYW1lcyhwc2V1ZG9idWxrX2ZpbGVzKSkKKQpgYGAKCiMjIyBSZWFkIGFuZCBwcmVwYXJlIGlucHV0IGRhdGEKCldlJ2xsIG1ha2UgYm90aCBhIGxvbmcgYW5kIHdpZGUgdmVyc2lvbiBvZiB0aGUgZGF0YSBmb3IgY29udmVuaWVuY2UgdGhyb3VnaG91dCB0aGUgbm90ZWJvb2suCgoKYGBge3J9CnByb2plY3RfZGZfbGlzdCA8LSBwdXJycjo6bWFwMigKICB0cG1fZmlsZXMsIAogIHBzZXVkb2J1bGtfZmlsZXMsIAogIFwodHBtX2ZpbGUsIHBzZXVkb19maWxlKSB7CiAgICAKICAgIHRwbV9kZiA8LSByZWFkcjo6cmVhZF90c3YodHBtX2ZpbGUsIHNob3dfY29sX3R5cGVzID0gRkFMU0UpIHw+CiAgICAgICMgIFRQTSBuZWVkcyB0byBiZSBpbiBsb2cyIHNwYWNlCiAgICAgIGRwbHlyOjptdXRhdGUoZXhwcmVzc2lvbiA9IGxvZzIoZXhwcmVzc2lvbikpCiAgICAKICAgIHBzZXVkb19kZiA8LSByZWFkcjo6cmVhZF90c3YocHNldWRvX2ZpbGUsIHNob3dfY29sX3R5cGVzID0gRkFMU0UpIAogICAgCiAgICBkcGx5cjo6YmluZF9yb3dzKAogICAgICB0cG1fZGYsIAogICAgICBwc2V1ZG9fZGYKICAgICkgfD4KICAgICAgdGlkeXI6OnBpdm90X3dpZGVyKAogICAgICAgIG5hbWVzX2Zyb20gPSBleHByZXNzaW9uX3R5cGUsIAogICAgICAgIHZhbHVlc19mcm9tID0gZXhwcmVzc2lvbgogICAgICApCiAgfQopCmBgYAoKV2UnbGwgcmVhZCBpbiB0aGUgYnVsayBjb3VudHMgZGF0YSBhbmQgY29tYmluZSB3aXRoIHRoZSByZXN0IG9mIHRoZSBkYXRhLgoKYGBge3J9CiMgd2Ugb25seSB3YW50IHRvIGtlZXAgdGhlc2Ugc2FtcGxlcyBmcm9tIHRoZSBidWxrIGNvdW50cwpwcmVzZW50X3NhbXBsZXMgPC0gcHJvamVjdF9kZl9saXN0IHw+CiAgcHVycnI6Om1hcCgKICAgIFwoZGYpIHsKICAgICAgZGYgfD4gCiAgICAgICAgZHBseXI6OnB1bGwoc2FtcGxlX2lkKSB8PiAKICAgICAgICB1bmlxdWUoKQogICAgfQogICkgfD4KICBwdXJycjo6cmVkdWNlKGMpCgojIE1ha2UgYSBsaXN0IG9mIGRhdGEgZnJhbWVzIG9mIGJ1bGsgY291bnRzLCBub3JtYWxpemVkIGJ5IERFU2VxMgpidWxrX2RmX2xpc3QgPC0gcmVhZHI6OnJlYWRfcmRzKGJ1bGtfY291bnRzX2ZpbGUpIHw+CiAgcHVycnI6Om1hcCgKICAgIFwoZGYpIHsKICAgICAgZGYgfD4KICAgICAgICBkcGx5cjo6ZmlsdGVyKHNhbXBsZV9pZCAlaW4lIHByZXNlbnRfc2FtcGxlcykgfD4KICAgICAgICBkcGx5cjo6cmVuYW1lKGVuc2VtYmxfaWQgPSBnZW5lX2lkKQogICAgfQogICkKCiMgQ29tYmluZSB0aGUgZGF0YQpwcm9qZWN0X2RmX2xpc3QgPC0gcHVycnI6Om1hcDIoCiAgYnVsa19kZl9saXN0LCAKICBwcm9qZWN0X2RmX2xpc3QsIAogIFwoZGZfY291bnRzLCBkZl9tYWluKSB7CiAgICAKICAgIGRmX21haW4gfD4KICAgICAgZHBseXI6OmxlZnRfam9pbihkZl9jb3VudHMsIGJ5ID0gYygiZW5zZW1ibF9pZCIsICJzYW1wbGVfaWQiKSkKICB9CikKYGBgCgoKIyMgRnVsbCBkaXN0cmlidXRpb25zCgpGaXJzdCwgd2UnbGwgdmlzdWFsaXplIGRpc3RyaWJ1dGlvbnMgb2YgYWxsIHF1YW50aXRpZXM6CgpgYGB7ciwgZmlnLndpZHRoID0gNywgZmlnLmhlaWdodCA9IDYuNSwgd2FybmluZyA9IEZBTFNFfQpwbG90X2RmIDwtIHByb2plY3RfZGZfbGlzdCB8PgogIHB1cnJyOjpsaXN0X3JiaW5kKG5hbWVzX3RvID0gInByb2plY3RfaWQiKSB8PiAKICB0aWR5cjo6cGl2b3RfbG9uZ2VyKAogICAgYygtcHJvamVjdF9pZCwgLWVuc2VtYmxfaWQsIC1zYW1wbGVfaWQpLCAKICAgIG5hbWVzX3RvID0gImV4cHJlc3Npb25fdHlwZSIsIAogICAgdmFsdWVzX3RvID0gImV4cHJlc3Npb24iCiAgKQoKZ2dwbG90KHBsb3RfZGYpICsgCiAgYWVzKHggPSBleHByZXNzaW9uLCBmaWxsID0gZXhwcmVzc2lvbl90eXBlKSArIAogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSkgKyAKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIikgKyAKICBmYWNldF9ncmlkKAogICAgcm93cyA9IHZhcnMoZXhwcmVzc2lvbl90eXBlKSwgCiAgICBjb2xzID0gdmFycyhwcm9qZWN0X2lkKSwKICAgIHNjYWxlcyA9ICJmcmVlX3kiCiAgKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCldlIHNlZSBiaWcgc3Bpa2VzIGF0IHplcm8gZm9yIHBzZXVkb2J1bGssIG5vdCBzdXJwcmlzaW5nbHkuCkR1ZSB0byB0aGUgZGlmZmVyZW50IHRyYW5zZm9ybWF0aW9uIGFwcHJvYWNoZXMsIHRoZSBgcHNldWRvYnVsa19kZXNlcWAgdmVyc2lvbiBoYXMgc29tZSBuZWdhdGl2ZXMgZm9yIGZyYWN0aW9uYWwgdmFsdWVzLCBidXQgdGhlIG90aGVyIHF1YW50aXRpZXMgaGF2ZSBhIGxvd2VyIGJvdW5kIG9mIHplcm8uCkFsbCBhcm91bmQsIGRpc3RyaWJ1dGlvbnMgYXJlIGNvbmNldHJhdGVkIHJhbmdlIGZyb20gdGhlaXIgbG93ZXIgYm91bmQgdG8gYXJvdW5kIDIwLCBzbyBpdCdzIG5pY2UgdG8ga25vdyBwc2V1ZG9idWxrIGFuZCBidWxrIGFyZSBkZWZpbml0ZWx5IG9uIHRoZSBzYW1lIHNjYWxlLgpJbiB0aGUgYnVsayBjb3VudHMsIGBTQ1BDUDAwMDAxN2Agc2VlbXMgdG8gaGF2ZSBhIG11Y2ggbGFyZ2VyIHBlYWsgYXQgemVybyBjb21wYXJlZCB0byBvdGhlciBwcm9qZWN0cy4KCiAgCkxldCdzIGNsZWFuIHVwIGZvciBtZW1vcnkgaGVyZS4KYGBge3J9CnJtKHBsb3RfZGYpCmdjKCkKYGBgCgojIyBSZWxhdGlvbnNoaXAgYmV0d2VlbiBxdWFudGl0aWVzCgpUaGlzIHNlY3Rpb24gd2lsbCBsb29rIGF0IHRoZSByZWxhdGlvbnNoaXAgYW1vbmcgcXVhbnRpdGllczoKCiogSG93IHNpbWlsYXIgYXJlIHRoZSBwc2V1ZG9idWxrIG1lYXN1cmVzIHRoZW1zZWx2ZXM/CiogSG93IGRvZXMgZWFjaCBwc2V1ZG9idWxrIG1lYXN1cmUgY29tcGFyZSB0byBidWxrIFRQTT8KICAqIEZvciB0aGVzZSBwbG90cywgd2UnbGwgYmluIGRhdGEgdG8gc2VlIHRoZSBjb25jZW50cmF0aW9uIG9mIG92ZXJsYXBwaW5nIHBvaW50cyBtb3JlIGVhc2lseS4KKiBIb3cgZG9lcyBlYWNoIHBzZXVkb2J1bGsgbWVhc3VyZSBjb21wYXJlIHRvIGJ1bGsgbm9ybWFsaXplZCBjb3VudHMgKG5vdCBUUE0pPwogICogRm9yIHRoZXNlIHBsb3RzLCB3ZSdsbCBiaW4gZGF0YSB0byBzZWUgdGhlIGNvbmNlbnRyYXRpb24gb2Ygb3ZlcmxhcHBpbmcgcG9pbnRzIG1vcmUgZWFzaWx5LgpJbiBhbGwgcGxvdHMsIHRoZSByZWQgbGluZSBpcyBgeT14YCwgYW5kIHRoZSBibHVlIGxpbmUgaXMgdGhlIHJlZ3Jlc3Npb24gbGluZS4KCgojIyMgQ29tcGFyZSBwc2V1ZG9idWxrIG1lYXN1cmVzCgpgYGB7ciBmaWcuaGVpZ2h0PTM0LCBmaWcud2lkdGg9OCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHJvamVjdF9kZl9saXN0IHw+CiAgcHVycnI6OmltYXAoCiAgICBcKGRmLCBwcm9qZWN0X2lkKSB7CiAgICAgIAogICAgICBnZ3Bsb3QoZGYpICsgCiAgICAgICAgYWVzKHggPSBwc2V1ZG9idWxrX2Rlc2VxLCB5ID0gcHNldWRvYnVsa19sb2dfY291bnRzKSArIAogICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjIsIHNpemUgPSAwLjUpICsgCiAgICAgICAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgbGluZXdpZHRoID0gMC41KSArCiAgICAgICAgZ2VvbV9hYmxpbmUobGluZXdpZHRoID0gMC41LCBjb2xvciA9ICJyZWQiKSArIAogICAgICAgIGZhY2V0X3dyYXAodmFycyhzYW1wbGVfaWQpLCBucm93ID0gNSkgKwogICAgICAgIGdndGl0bGUocHJvamVjdF9pZCkgCgogICAgfQogICkgfD4KICBwYXRjaHdvcms6OndyYXBfcGxvdHMobmNvbCA9IDEpCmBgYAoKClRoZXNlIHF1YW50aXRpZXMgYXJlIGV4Y2VwdGlvbmFsbHkgc2ltaWxhciB3aXRoIHRoZXNlIGRpZmZlcmVuY2VzOgoKKiBEcml2ZW4gYnkgZGlmZmVyZW50IG5vcm1hbGl6YXRpb24gYXBwcm9hY2hlcywgZ2VuZXMgd2l0aCB2ZXJ5IGxvdyB0byB6ZXJvIGV4cHJlc3Npb24KKiBJbiBhIGhhbmRmdWwgb2Ygc2FtcGxlcyAoMS0yIHBlciBwcm9qZWN0KSwgYHBzZXVkb2J1bGtfbG9nX2NvdW50c2AgYXBwZWFycyB0byBoYXZlIGEgaGlnaGVyIHByb3BvcnRpb24gb2YgbG93IHRvIHplcm8gY291bnRzLCBhbmQgdGhyb3VnaG91dCBoYXMgbG93ZXIgdmFsdWVzIHRoYW4gYHBzZXVkb2J1bGtfZGVzZXFgLgoKCgojIyMgQ29tcGFyZSBwc2V1ZG9idWxrIHRvIGJ1bGsKClNpbmNlIHRoZXNlIHF1YW50aXRpZXMgaGVyZSBhcmUgc28gc2ltaWxhciwgdGhlc2Ugc2NhdHRlcnBsb3RzIHdpbGwgc2hvdyBvbmx5IGJ1bGsgY29tcGFyaXNvbnMgdG8gYHBzZXVkb2J1bGtfZGVzZXFgLgoKVGhlc2UgcGxvdHMgd2lsbCBzaG93OgoKKiBMZWZ0IHBhbmVsOiBgYnVsayBUUE0gfiBwc2V1ZG9idWxrX2Rlc2VxYAoqIFJpZ2h0IHBhbmVsOiBgYnVsayBjb3VudHMgfiBwc2V1ZG9idWxrX2Rlc2VxYAoKYGBge3J9CiMgSGVscGVyIGZ1bmN0aW9uIHRvIHZpc3VhbGl6ZSBzY2F0dGVycGxvdHMgd2l0aCBnZW9tX2Jpbl8yZCgpCm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cyA8LSBmdW5jdGlvbihkZiwgcHJvamVjdF9pZCwgbmJpbnMsIGZhY2V0X3Jvd3MpIHsKICBwMSA8LSBnZ3Bsb3QoZGYpICsgCiAgICBhZXMoeCA9IHBzZXVkb2J1bGtfZGVzZXEsIHkgPSBidWxrX3RwbSkgKyAKICAgIGdlb21fYmluXzJkKGJpbnMgPSBuYmlucykgKyAKICAgIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGFscGhhID0gMC44LCBsaW5ld2lkdGggPSAwLjUpICsKICAgIGdlb21fYWJsaW5lKGFscGhhID0gMC44LCBsaW5ld2lkdGggPSAwLjUsIGNvbG9yID0gInJlZCIpICsgCiAgICBmYWNldF93cmFwKHZhcnMoc2FtcGxlX2lkKSwgbnJvdyA9IGZhY2V0X3Jvd3MpICsKICAgIGdndGl0bGUoImJ1bGtfdHBtIH4gZGVzZXEiKSAKICAKICBwMiA8LSBnZ3Bsb3QoZGYpICsgCiAgICBhZXMoeCA9IHBzZXVkb2J1bGtfZGVzZXEsIHkgPSBidWxrX2NvdW50cykgKyAKICAgIGdlb21fYmluXzJkKGJpbnMgPSBuYmlucykgKyAKICAgIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGFscGhhID0gMC44LCBsaW5ld2lkdGggPSAwLjUpICsKICAgIGdlb21fYWJsaW5lKGFscGhhID0gMC44LCBsaW5ld2lkdGggPSAwLjUsIGNvbG9yID0gInJlZCIpICsgCiAgICBmYWNldF93cmFwKHZhcnMoc2FtcGxlX2lkKSwgbnJvdyA9IGZhY2V0X3Jvd3MpICsKICAgIGdndGl0bGUoImJ1bGtfY291bnRzIH4gZGVzZXEiKSAKCgogIHBhdGNod29yazo6d3JhcF9wbG90cyhwMSwgcDIsIG5jb2wgPSAyKSArIHBhdGNod29yazo6cGxvdF9hbm5vdGF0aW9uKHRpdGxlID0gcHJvamVjdF9pZCkKfQpgYGAKCgoKCmBgYHtyIGZpZy5oZWlnaHQgPSAxMiwgZmlnLndpZHRoID0gMTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cygKICBwcm9qZWN0X2RmX2xpc3QkU0NQQ1AwMDAwMDEsIAogIHByb2plY3RfaWQgPSAiU0NQQ1AwMDAwMDEiLAogIG5iaW5zID0gMTUsCiAgZmFjZXRfcm93cyA9IDYKKQpgYGAKCgoKYGBge3IgZmlnLmhlaWdodCA9IDEwLCBmaWcud2lkdGggPSAxMiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbWFrZV9iaW5uZWRfc2NhdHRlcnBsb3RzKAogIHByb2plY3RfZGZfbGlzdCRTQ1BDUDAwMDAwMiwgCiAgcHJvamVjdF9pZCA9ICJTQ1BDUDAwMDAwMiIsCiAgbmJpbnMgPSAxNSwKICBmYWNldF9yb3dzID0gNgopCmBgYAoKCmBgYHtyIGZpZy5oZWlnaHQgPSAxMiwgZmlnLndpZHRoID0gMTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cygKICBwcm9qZWN0X2RmX2xpc3QkU0NQQ1AwMDAwMDYsIAogIHByb2plY3RfaWQgPSAiU0NQQ1AwMDAwMDYiLAogIG5iaW5zID0gMTUsCiAgZmFjZXRfcm93cyA9IDkKKQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0ID0gMywgZmlnLndpZHRoID0gMTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cygKICBwcm9qZWN0X2RmX2xpc3QkU0NQQ1AwMDAwMDksIAogIHByb2plY3RfaWQgPSAiU0NQQ1AwMDAwMDkiLAogIG5iaW5zID0gMTUsCiAgZmFjZXRfcm93cyA9IDEKKQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0ID0gOCwgZmlnLndpZHRoID0gMTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cygKICBwcm9qZWN0X2RmX2xpc3QkU0NQQ1AwMDAwMTcsIAogIHByb2plY3RfaWQgPSAiU0NQQ1AwMDAwMTciLAogIG5iaW5zID0gNDAsCiAgZmFjZXRfcm93cyA9IDcKKQpgYGAKCiogYGJ1bGtfY291bnRzYCBnZW5lcmFsbHkgaGFzIG11Y2ggY2xvc2VyIHRvIGEgMToxIHJlbGF0aW9uc2hpcCB3aXRoIHBzZXVkb2J1bGsgY29tcGFyZWQgdG8gYGJ1bGtfdHBtYAogICogVGhpcyBpcyBuZWFybHkgdW5pdmVyc2FsbHkgdGhlIGNhc2UgZm9yIGBTQ1BDUDAwMDAwMWAsIGBTQ1BDUDAwMDAwMmAsIGFuZCBgU0NQQ1AwMDAwMDZgLCBhbmQgd2hpbGUgdHJ1ZSBmb3IgYFNDUENQMDAwMDA5YCBhbmQgYFNDUENQMDAwMDE3YCwgaXQgaXMgbGVzcyBwcm9ub3VuY2VkCiogUmVsYXRpb25zaGlwcyBpbiBnZW5lcmFsIGFyZSB3ZWFrZXN0IGZvciBgU0NQQ1AwMDAwMTdgLCBidXQgdGhlIHNpdHVhdGlvbiBkb2VzIGxvb2sgYmV0dGVyIHdpdGggYGJ1bGtfY291bnRzYAoKCiMjIyMgU3RhdGlzdGljcwoKTGV0J3MgbmFpbCB0aGlzIGRvd24gZnVydGhlciB3aXRoIGNvcnJlbGF0aW9ucyBjb21wYXJpbmcgZWFjaCBtZWFzdXJlIGZvciBidWxrIHdpdGggZWFjaCBtZWFzdXJlIGZvciBwc2V1ZG9idWxrLgpXZSdsbCBwZXJmb3JtIGNvcnJlbGF0aW9ucyBvbiBhIHBlci1zYW1wbGUgYmFzaXMsIGJvdGggcGFyYW1ldHJpYyBhbmQgbm9uLXBhcmFtZXRyaWMsIGRpc3BsYXkgc29tZSBjb3JyZWxhdGlvbnMgYmVsb3cgYm90aCBhcyBib3hwbG90cyBhbmQgdGhlIGZ1bGwgdGFibGUuCgpgYGB7ciBmaWcuaGVpZ2h0ID0gMTAsIGZpZy53aWR0aCA9IDZ9CgojIEhlbHBlciBmdW5jdGlvbiB0byBydW4gY29ycmVsYXRpb25zCm1vZGVsX3NhbXBsZXMgPC0gZnVuY3Rpb24oaWQsIGRmKSB7CiAgc2FtcGxlX2RmIDwtIGRmIHw+CiAgICBkcGx5cjo6ZmlsdGVyKHNhbXBsZV9pZCA9PSBpZCkgCiAgCiAgIyMjIyBCdWxrIHRwbQogIGRmX2Rlc2VxIDwtIHNhbXBsZV9kZiB8PgogICAgZHBseXI6OmZpbHRlcihpcy5maW5pdGUocHNldWRvYnVsa19kZXNlcSksIGlzLmZpbml0ZShidWxrX3RwbSkpCiAgcl9kZXNlcV90cG0gPC0gY29yKGRmX2Rlc2VxJGJ1bGtfdHBtLCBkZl9kZXNlcSRwc2V1ZG9idWxrX2Rlc2VxLCBtZXRob2QgPSAic3BlYXJtYW4iKQogIHJob19kZXNlcV90cG0gPC0gY29yKGRmX2Rlc2VxJGJ1bGtfdHBtLCBkZl9kZXNlcSRwc2V1ZG9idWxrX2Rlc2VxLCBtZXRob2QgPSAicGVhcnNvbiIpCgogIGRmX2xvZ19jb3VudHMgPC0gc2FtcGxlX2RmIHw+CiAgICAgIGRwbHlyOjpmaWx0ZXIoaXMuZmluaXRlKHBzZXVkb2J1bGtfbG9nX2NvdW50cyksIGlzLmZpbml0ZShidWxrX3RwbSkpCiAgcl9sb2djb3VudHNfdHBtIDwtIGNvcihkZl9kZXNlcSRidWxrX3RwbSwgZGZfZGVzZXEkcHNldWRvYnVsa19sb2dfY291bnRzLCBtZXRob2QgPSAic3BlYXJtYW4iKQogIHJob19sb2djb3VudHNfdHBtIDwtIGNvcihkZl9kZXNlcSRidWxrX3RwbSwgZGZfZGVzZXEkcHNldWRvYnVsa19sb2dfY291bnRzLCBtZXRob2QgPSAicGVhcnNvbiIpCiAgICAgIAogICMjIyMgQnVsayBjb3VudHMKICBkZl9kZXNlcSA8LSBzYW1wbGVfZGYgfD4KICAgIGRwbHlyOjpmaWx0ZXIoaXMuZmluaXRlKHBzZXVkb2J1bGtfZGVzZXEpLCBpcy5maW5pdGUoYnVsa19jb3VudHMpKQogIHJfZGVzZXFfY291bnRzIDwtIGNvcihkZl9kZXNlcSRidWxrX2NvdW50cywgZGZfZGVzZXEkcHNldWRvYnVsa19kZXNlcSwgbWV0aG9kID0gInNwZWFybWFuIikKICByaG9fZGVzZXFfY291bnRzIDwtIGNvcihkZl9kZXNlcSRidWxrX2NvdW50cywgZGZfZGVzZXEkcHNldWRvYnVsa19kZXNlcSwgbWV0aG9kID0gInBlYXJzb24iKQoKICBkZl9sb2dfY291bnRzIDwtIHNhbXBsZV9kZiB8PgogICAgICBkcGx5cjo6ZmlsdGVyKGlzLmZpbml0ZShwc2V1ZG9idWxrX2xvZ19jb3VudHMpLCBpcy5maW5pdGUoYnVsa19jb3VudHMpKQogIHJfbG9nY291bnRzX2NvdW50cyA8LSBjb3IoZGZfZGVzZXEkYnVsa19jb3VudHMsIGRmX2Rlc2VxJHBzZXVkb2J1bGtfbG9nX2NvdW50cywgbWV0aG9kID0gInNwZWFybWFuIikKICByaG9fbG9nY291bnRzX2NvdW50cyA8LSBjb3IoZGZfZGVzZXEkYnVsa19jb3VudHMsIGRmX2Rlc2VxJHBzZXVkb2J1bGtfbG9nX2NvdW50cywgbWV0aG9kID0gInBlYXJzb24iKQogICAgICAKICAjIFRhYnVsYXRlIGFuZCByZXR1cm4gc29tZSBmaXQgc3RhdHMKICBkYXRhLmZyYW1lKAogICAgciA9ICAgICAgYyhyX2Rlc2VxX3RwbSwgICAgcl9sb2djb3VudHNfdHBtLCAgICByX2Rlc2VxX2NvdW50cywgICByX2xvZ2NvdW50c19jb3VudHMpLAogICAgcmhvID0gICAgYyhyaG9fZGVzZXFfdHBtLCAgcmhvX2xvZ2NvdW50c190cG0sICByaG9fZGVzZXFfY291bnRzLCByaG9fbG9nY291bnRzX2NvdW50cyksCiAgICBidWxrID0gICBjKCJidWxrX3RwbSIsICAgICJidWxrX3RwbSIsICAgICAgICAgICJidWxrX2NvdW50cyIsICAgICJidWxrX2NvdW50cyIpLCAKICAgIHBzZXVkbyA9IGMoImRlc2VxIiwgICAgICAgImxvZ19jb3VudHMiLCAgICAgICAgImRlc2VxIiwgICAgICAgICAgImxvZ19jb3VudHMiKQogICkgfD4KICAgIHRpZHlyOjpwaXZvdF9sb25nZXIoCiAgICAgIGMociwgcmhvKSwgCiAgICAgIG5hbWVzX3RvID0gIm1lYXN1cmUiLCAKICAgICAgdmFsdWVzX3RvID0gImNvcnJlbGF0aW9uIgogICAgKQp9CgpzdGF0c19kZiA8LSBwcm9qZWN0X2RmX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChkZikgewogICAgICAKICAgICAgIyBXZSBuZWVkIHRvIG1hcCBvdmVyIHNhbXBsZSBpZHMgbm93CiAgICAgIHNhbXBsZXMgPC0gdW5pcXVlKGRmJHNhbXBsZV9pZCkKICAgICAgbmFtZXMoc2FtcGxlcykgPC0gc2FtcGxlcwogICAgICAKICAgICAgZml0X3RhYmxlIDwtIHNhbXBsZXMgfD4KICAgICAgICBwdXJycjo6bWFwKG1vZGVsX3NhbXBsZXMsIGRmKSB8PgogICAgICAgIHB1cnJyOjpsaXN0X3JiaW5kKG5hbWVzX3RvID0gInNhbXBsZV9pZCIpCiAgICAgIAogICAgICByZXR1cm4oZml0X3RhYmxlKQoKICAgIH0KICApIHw+CiAgIyBub3csIGNvbWJpbmUgYWxsIHByb2plY3RzIGludG8gYSBzaW5nbGUgdGFibGUKICBwdXJycjo6bGlzdF9yYmluZChuYW1lc190byA9ICJwcm9qZWN0X2lkIikgfD4KICBkcGx5cjo6bXV0YXRlKGNvbXBhcmlzb24gPSBnbHVlOjpnbHVlKCJ7YnVsa30te3BzZXVkb30iKSkgfD4KICBkcGx5cjo6c2VsZWN0KC1idWxrLCAtcHNldWRvKQoKCmdncGxvdChzdGF0c19kZikgKyAKICBhZXMoeCA9IGNvbXBhcmlzb24sIGZpbGwgPSBjb21wYXJpc29uLCB5ID0gY29ycmVsYXRpb24pICsgCiAgZ2VvbV9ib3hwbG90KGxpbmV3aWR0aCA9IDAuMjUsIG91dGxpZXIuc2l6ZSA9IDAuMjUpKwogIGZhY2V0X2dyaWQoCiAgICByb3dzID0gdmFycyhwcm9qZWN0X2lkKSwgCiAgICBjb2xzID0gdmFycyhtZWFzdXJlKSkgKyAKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwgCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDMwLCBoanVzdCA9IDEpCiAgKQpgYGAKCiogUmVsYXRpb25zaGlwcyBiZXR3ZWVuIHBzZXVkb2J1bGsgYW5kIGJ1bGsgY291bnRzIGFyZSBleGNlcHRpb25hbGx5IHNpbWlsYXIgaGVyZSwgYW5kIGZ1cnRoZXIgd2UgYXJlIGdlbmVyYWxseSBseWluZyBhbG9uZyB0aGUgMToxIGxpbmUuCiogRm9yIGFsbCBwcm9qZWN0cywgdGhlIG5vbnBhcmFtZXRyaWMgYXBwcm9hY2ggeWllbGRzIG1hcmdpbmFsbHkgaGlnaGVyIGNvcnJlbGF0aW9ucywgYnV0IG9mdGVuIG1hcmdpbmFsbHkKKiBVc2luZyBhIG5vbnBhcmFtZXRyaWMgdGVzdCB5aWVsZHMgc3Ryb25nZXIgY29ycmVsYXRpb25zIGZvciBgYnVsa19jb3VudHNgIG5vdCBidXQgZm9yIGBidWxrX3RwbWAKKiBGb3IgYFNDUENQMDAwMDAxYCwgYFNDUENQMDAwMDAyYCwgYW5kIGBTQ1BDUDAwMDAwNmAsIHRoZSBjb3JyZWxhdGlvbnMgYXJlIGdlbmVyYWxseSBzaW1pbGFyIHJlZ2FyZGxlc3Mgb2Ygd2hpY2ggcXVhbnRpdGllcyBhcmUgYmVpbmcgY29tcGFyZWQgYW5kIHdoaWNoIHN0YXRpc3RpYyBpcyB1c2VkLCBhbHRob3VnaCB0aGVyZSBhcmUgc29tZSBzbWFsbCBkaWZmZXJlbmNlcyAobm90IGxpa2VseSBzaWduaWZpY2FudCkKKiBGb3IgYFNDUENQMDAwMDA5YCAoYWdhaW4sIG9ubHkgMyBzYW1wbGVzIGhlcmUhKSBhbmQgYFNDUENQMDAwMDE3YCwgYGJ1bGtfY291bnRzYCB0ZW5kcyB0byBvdXRwZXJmb3JtIGBidWxrX3RwbWAgZm9yIGVpdGhlciBwc2V1ZG9idWxrIG1lYXN1cmUKClRoZSB1bmRlcmx5aW5nIGNvcnJlbGF0aW9ucyBhcmUgaGVyZToKCgpgYGB7cn0Kc3RhdHNfZGYKYGBgCgoKCiMjIERpc2FncmVlaW5nIGV4cHJlc3Npb24KCkJhc2VkIG9uIHBlcmZvcm1hbmNlcyBvZiBxdWFudGl0aWVzIGFib3ZlLCB0aGlzIHNlY3Rpb24gY29uc2lkZXJzIHNwZWNpZmljYWxseSBgYnVsa19jb3VudHNgIGFuZCBgcHNldWRvYnVsa19sb2dfY291bnRzYC4KCk5leHQsIHdlJ2xsIHRha2UgYSBxdWljayBsb29rIGF0IGNhc2VzIHdoZXJlIG9uZSBtb2RhbGl0eSBoYXMgemVybyBleHByZXNzaW9uIGFuZCB0aGUgb3RoZXIgZG9lc24ndC4KSW4gdGhlc2UgY2FzZXMsIGlmIGV4cHJlc3Npb24gaXMgZ2VuZXJhbGx5IGhpZ2gsIHdlIGhhdmUgZXZpZGVuY2Ugb2YgZGlzYWdyZWVtZW50L2Rpc2NyZXBhbmN5IGJldHdlZW4gYnVsayBhbmQgc2luZ2xlLWNlbGwgdGhhdCBtYXkgYmUgaW50ZXJlc3RpbmcgdG8gaW52ZXN0aWdhdGUuCkluIHRoaXMgbm90ZWJvb2ssIHdlJ2xsIGp1c3QgYSBzZW5zZSBvZiBob3cgbXVjaCAidGhlcmUgaXMgdGhlcmUsIiBhbmQgd2UnbGwgbGVhdmUgdGhlIGluLWRlcHRoIGxvb2sgaW50byBhbnkgc3VjaCBnZW5lcyBmb3IgYSBzdWJzZXF1ZW50IG5vdGVib29rLgoKSW4gdGhpcyBzZWN0aW9uLCB3ZSdsbCBhbHNvIHVzZSBhIHRocmVzaG9sZCBvZiBgMWUtMTJgIGZvciB6ZXJvIGhlcmUuCgoKIyMjIEJ1bGsgY291bnRzIHdoZW4gc2luZ2xlLWNlbGwgaXMgemVybwoKCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcGxvdF9kZiA8LSBwdXJycjo6bGlzdF9yYmluZChwcm9qZWN0X2RmX2xpc3QsIG5hbWVzX3RvID0gInByb2plY3RfaWQiKSB8PgogIGRwbHlyOjpmaWx0ZXIoCiAgICBwc2V1ZG9idWxrX2xvZ19jb3VudHMgPD0gMWUtMTIsIAogICAgYnVsa19jb3VudHMgPiAxZS0xMgogICkgIAogIApnZ3Bsb3QocGxvdF9kZikgKyAKICBhZXMoeCA9IHNhbXBsZV9pZCwgeSA9IGJ1bGtfY291bnRzKSArIAogIGdlb21fYm94cGxvdChvdXRsaWVyLnNpemUgPSAwLjI1KSArCiAgZmFjZXRfd3JhcCh2YXJzKHByb2plY3RfaWQpLCBuY29sID0gMSwgc2NhbGUgPSAiZnJlZSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgoKCiMjIyBTaW5nbGUtY2VsbCB3aGVuIGJ1bGsgY291bnRzIGlzIHplcm8KCgpgYGB7ciBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnBsb3RfZGYgPC0gcHVycnI6Omxpc3RfcmJpbmQocHJvamVjdF9kZl9saXN0LCBuYW1lc190byA9ICJwcm9qZWN0X2lkIikgfD4KICBkcGx5cjo6ZmlsdGVyKAogICAgYnVsa19jb3VudHMgPD0gMWUtMTIsIAogICAgcHNldWRvYnVsa19sb2dfY291bnRzID4gMWUtMTIKICApICAKICAKZ2dwbG90KHBsb3RfZGYpICsgCiAgYWVzKHggPSBzYW1wbGVfaWQsIHkgPSBwc2V1ZG9idWxrX2xvZ19jb3VudHMpICsgCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2l6ZSA9IDAuMjUpICsKICBmYWNldF93cmFwKHZhcnMocHJvamVjdF9pZCksIG5jb2wgPSAxLCBzY2FsZSA9ICJmcmVlIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCgoKRnJvbSBib3RoIGNvbXBhcmlzb25zLCB0aGVyZSdzIGEgZGVjZW50IG51bWJlciBvZiBnZW5lcyB3aXRoIGhpZ2ggZXhwcmVzc2lvbiBpbiBvbmUgbW9kYWxpdHkgYW5kIGVzc2VudGlhbGx5IHplcm8gaW4gdGhlIG90aGVyLiAKQSBtb3JlIGNhcmVmdWwgaW52ZXN0aWdhdGlvbiBoZXJlIGxvb2sgaW50byB3aGF0IGV4YWN0bHkgdGhlc2UgZ2VuZXMgYXJlLCBhbmQgd2hldGhlciB0aGV5IGhhdmUgc29tZSBiaW9sb2dpY2FsIHJlbGF0aW9uc2hpcCB0aGF0IG1pZ2h0IHN1Z2dlc3QgbW9kYWxpdGllcyBhcmUgcGlja2luZyB1cCBkaWZmZXJlbnQgaW5mb3JtYXRpb24uCgoKCiMjIFNlc3Npb24gaW5mbwoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBg